﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;

namespace NRemedy.Test.Util
{
    /// <summary>
    /// try to (('Submitter' = \"Parker Zhou\") AND ('Integer Field' < 2)) to lamda expression
    /// </summary>
    public class ExpressionBuilder
    {
        Type type;
        Expression[] stack;
        int top = 0;
        ParameterExpression _pe;

        public ExpressionBuilder()
        {

 
        }

        public Expression ConventExpressoin(string Qulification,Type type)
        {
            stack = new Expression[Qulification.Length];
            _pe = Expression.Parameter(type, "p");
            int begin = -1;
            char[] chars = Qulification.ToCharArray();
            for (int i = 0; i < chars.Length;i++ )
            {
                switch (chars[i])
                {
                    case '(':
                        Push(new LeftBracketsExpression());
                        break;
                    case ')':
                        if (begin != -1)
                        {
                            //from begin to i-1
                            string s = new string((chars.Skip(begin).Take(i - begin)).ToArray());
                            Push(new StringExpression(s));

                            //reset begin
                            begin = -1;
                        }

                        //begin to pop util LeftBracketsExpression
                        Expression first = Pop();
                        if (first is StringExpression)
                        {
                            //string expect '('
                            Expression leftbracket = Pop();
                            if (!(leftbracket is LeftBracketsExpression)) throw new InvalidCastException();

                            //convert StringExpression to binaryexpression and push
                            Push(((StringExpression)first).ConventToBinaryExpression(_pe,type));

                        }
                        else if (first is BinaryExpression)
                        {
                            Expression t = Pop();
                            if(t is LeftBracketsExpression) 
                            {
                                Push(first);
                            }
                            else if (t is AndExpression)
                            {
                                Expression exp = Pop();
                                if(exp is BinaryExpression)
                                {
                                    Pop();//pop '('
                                    Push(Expression.AndAlso(exp,first));
                                }
 
                            }
                            else if (t is OrExpression)
                            {
                                Expression exp = Pop();
                                if (exp is BinaryExpression)
                                {
                                    Pop();//pop '('
                                    Push(Expression.OrElse(exp, first));
                                }

                            }
                        }
                        break;
                    case ' ':
                        if (begin != -1) //indicate has begin to catch literal
                        {
                            string s = new string((chars.Skip(begin).Take(i - begin)).ToArray());
                            if (s.Trim() == "AND") { Push(new AndExpression()); begin = -1; }
                            if (s.Trim() == "OR") { Push(new OrExpression()); begin = -1; }
 
                        }
                        break;
                    default:
                        if (begin == -1) //new string will be scaned
                        {
                            begin = i;
                        }
                        break;

                }

            }

            if (top != 1) throw new InvalidCastException();
            return Expression.Lambda(Pop(), _pe);
        }

        private void Push(Expression expression)
        {
            if (top != -1)
                stack[top] = expression;
            top++;
        }


        private Expression Pop()
        {
            if (top > 0)
            {
                --top;
                return stack[top];
            }
            return null;
        }

        //indicate '(' in stack
        private class LeftBracketsExpression : Expression
        {
 
        }

        //indicate 'Submitter' = "Parker" between '(' and ')'
        private class StringExpression : Expression
        {
            private string _String;
            public StringExpression(string _String)
            {
                this._String = _String;
            }

            public BinaryExpression ConventToBinaryExpression(ParameterExpression pe,Type targetType)
            {
                string[] block = new string[3];
                //'a' ? ...
                char[] chars = _String.ToArray();
                int begin = -1;
                int end = -1;
                //block[0]
                for (int i = 0; i < chars.Length; i++)
                {
                    if (chars[i] == '\'')
                    {
                        //find first ' and break;
                        begin = i;
                        break;
                    }
                }

                for (int i = begin + 1; i < chars.Length; )
                {
                    if(chars[i] == '\'' && (i == chars.Length - 1 || chars[i + 1] != '\''))
                    {
                        //find second ' and break
                        end = i;
                        break;
                    }
                    else if (chars[i] == '\'' && chars[i + 1] == '\''){
                        i += 2;
                    }
                    i++;
                }
                block[0] = new string(chars.Skip(begin).Take(end - begin+1).ToArray());

                //block[1]
                for (int i = end + 1; i < chars.Length; i++)
                {
                    if (chars[i] != ' ')
                    {
                        //find first non-empty and break;
                        begin = i;
                        break;
                    }
                }
                for (int i = begin + 1; i < chars.Length;i++ )
                {
                    if (chars[i] == ' ')
                    {
                        //find first non-empty and break;
                        end = i;
                        break;
                    }
                }
                block[1] = new string(chars.Skip(begin).Take(end - begin).ToArray());

                //block[2] is from end to end
                block[2] = new string(chars.Skip(end).ToArray());

                foreach (PropertyInfo prop in targetType.GetProperties
                    (BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance))
                {
                    var fieldAttribute = GetARAttributeField(prop, ModelBinderAccessLevel.OnlyBind);
                    if (fieldAttribute == null) continue;
                    if (fieldAttribute.DatabaseName == block[0].Trim('\''))
                    {
                        MemberExpression me = Expression.Property(pe, prop.Name);
                        string value = block[2].Replace("\\", "").Replace("\"", "").Trim();
                        object constant = null;
                        if (prop.PropertyType.IsEnum)
                        {
                            constant = Enum.Parse(prop.PropertyType, value);
                        }
                        else if(value != "$NULL$")
                        {
                            constant = System.Convert.ChangeType(value, prop.PropertyType);
                        }

                        switch (block[1])
                        {
                            case "=":
                                return Expression.Equal(me, Expression.Constant(constant));
                            case "<":
                                return Expression.LessThan(me, Expression.Constant(constant));
                            case "<=":
                                return Expression.LessThanOrEqual(me, Expression.Constant(constant));
                            case ">":
                                return Expression.GreaterThan(me, Expression.Constant(constant));
                            case ">=":
                                return Expression.GreaterThanOrEqual(me, Expression.Constant(constant));
                            default:
                                break;
                        }

                    }

                }
                
                return null;
            }

            private ARFieldAttribute GetARAttributeField(PropertyInfo pi, ModelBinderAccessLevel accessLevel)
            {
                if (pi == null)
                    throw new ArgumentNullException("pi");
                var attrs = pi.GetCustomAttributes(typeof(ARFieldAttribute), false);
                if (attrs.Length > 1)
                    throw new CustomAttributeFormatException(
                        string.Format("Mutiple ARFieldAttribute is found on Property : {0}.", pi.Name));
                if (attrs.Length == 0) return null;
                ARFieldAttribute attribute = attrs.First() as ARFieldAttribute;
                if (attribute.DatabaseID == 0)
                    throw new CustomAttributeFormatException(
                        string.Format("DatabaseID of ARFieldAttribute on Property : {0} is missing.", pi.Name));
                if (attribute.DataType == ARType.None)
                    throw new CustomAttributeFormatException(
                        string.Format("DataType of ARFieldAttribute on Property : {0} cannot be null.", pi.Name));
                if ((attribute.BinderAccess & accessLevel) == accessLevel)
                    return attribute;
                else
                    return null;
            }
        }

        private class AndExpression : Expression
        {
 
        }

        private class OrExpression : Expression
        {

        }
    }
}
